package clock.socoolby.com.clock.todo.microsoft;

import android.app.Activity;
import android.content.Context;
import android.util.Log;

import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.microsoft.identity.client.AuthenticationCallback;
import com.microsoft.identity.client.IAccount;
import com.microsoft.identity.client.IAuthenticationResult;
import com.microsoft.identity.client.PublicClientApplication;
import com.microsoft.identity.client.exception.MsalClientException;
import com.microsoft.identity.client.exception.MsalException;
import com.microsoft.identity.client.exception.MsalServiceException;
import com.microsoft.identity.client.exception.MsalUiRequiredException;

import org.json.JSONObject;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import clock.socoolby.com.clock.R;
import clock.socoolby.com.clock.net.HttpMethod;
import clock.socoolby.com.clock.net.NetworkManager;
import clock.socoolby.com.clock.net.auth.AuthCallback;
import clock.socoolby.com.clock.net.base.I_ResponseState;
import clock.socoolby.com.clock.net.listener.RequestListener;
import clock.socoolby.com.clock.net.listener.StateAbleRequestListener;
import clock.socoolby.com.clock.todo.I_TodoSyncService;
import clock.socoolby.com.clock.todo.microsoft.bean.attachment.Attachment;
import clock.socoolby.com.clock.todo.microsoft.bean.todo.TodoEntity;
import clock.socoolby.com.clock.todo.microsoft.bean.todo.TodoFolder;
import clock.socoolby.com.clock.todo.microsoft.bean.todo.TodoGroup;
import clock.socoolby.com.clock.todo.microsoft.listener.TodoEntityListRequestListener;
import clock.socoolby.com.clock.todo.microsoft.query.QBFBuilder;
import e.odbo.data.dsl.query.QBFParameter;

public class MicrosoftTodoSyncServiceImpl implements I_TodoSyncService {
    private static final String TAG = MicrosoftTodoSyncServiceImpl.class.getSimpleName();

    public static final String NAME="Microsoft Todo Service";

    /* Azure AD v2 Configs */
    final static String[] SCOPES = {"https://graph.microsoft.com/Tasks.ReadWrite","https://graph.microsoft.com/Tasks.Read","https://graph.microsoft.com/Tasks.Read.Shared","https://graph.microsoft.com/Tasks.ReadWrite.Shared"};

    final static String BASE_URL = "https://graph.microsoft.com/beta/me/outlook";
    final static String TASK_ENDPOINT ="/tasks";
    final static String TASK_GROUP_ENDPOINT ="/taskGroups";
    final static String TASK_FOLDER_ENDPOINT ="/taskFolders";
    final static String TASK_ATTACHMENT_ENDPOINT ="/attachments";


    /* Azure AD Variables */
    private PublicClientApplication sampleApp;
    private IAuthenticationResult authResult;
    private NetworkManager networkManager;

    private boolean serviceStarting =false;

    @Override
    public String getServiceName() {
        return NAME;
    }

    public void start(Context applicationContext, NetworkManager networkManager){
        timber.log.Timber.d("on start start...");
        /* Configure your sample app and save state for this activity */
        serviceStarting =true;
        sampleApp = new PublicClientApplication(
                applicationContext,
                R.raw.auth_config);
        timber.log.Timber.d("create sampleApp...");
        this.networkManager=networkManager;

        /* Attempt to get a user and acquireTokenSilent
         * If this fails we do an interactive request
         */
        sampleApp.getAccounts(new PublicClientApplication.AccountsLoadedCallback() {
            @Override
            public void onAccountsLoaded(final List<IAccount> accounts) {
                if (!accounts.isEmpty()) {
                    /* This sample doesn't support multi-account scenarios, use the first account */
                    timber.log.Timber.d("on accounts load");
                    sampleApp.acquireTokenSilentAsync(SCOPES, accounts.get(0), getAuthTokenCallback());
                } else {
                    /* No accounts or >1 account */
                }
                serviceStarting =false;
            }
        });
        timber.log.Timber.d("call getAccounts end");
    }

    public void signIn(Activity activity,AuthCallback authCallback){
        sampleApp.acquireToken(activity, SCOPES, getAuthTokenCallback(authCallback));
    }

    /* Clears an account's tokens from the cache.
     * Logically similar to "sign out" but only signs out of this app.
     * User will get interactive SSO if trying to sign back-in.
     */
    public void signOut() {
        /* Attempt to get a user and acquireTokenSilent
         * If this fails we do an interactive request
         */
        sampleApp.getAccounts(new PublicClientApplication.AccountsLoadedCallback() {
            @Override
            public void onAccountsLoaded(final List<IAccount> accounts) {

                if (accounts.isEmpty()) {
                    /* No accounts to remove */

                } else {
                    for (final IAccount account : accounts) {
                        sampleApp.removeAccount(
                                account,
                                new PublicClientApplication.AccountsRemovedCallback() {
                                    @Override
                                    public void onAccountsRemoved(Boolean isSuccess) {
                                        if (isSuccess) {
                                            /* successfully removed account */
                                        } else {
                                            /* failed to remove account */
                                        }
                                    }
                                });
                    }
                }
            }
        });
    }

    public boolean isSignIn(){
        timber.log.Timber.d("call is sing in...serviceStarting:"+ serviceStarting);
        return authResult != null&&authResult.getAccessToken() != null;
    }



    /* Use Volley to make an HTTP request to the /me endpoint from MS Graph using an access token */
    protected void callGraphAPI(HttpMethod methodType, String graphUrl, RequestListener listener) {
        /* Make sure we have a token to send to graph */
        if (authResult.getAccessToken() == null) {
            timber.log.Timber.d("call graph api please sign in first ..url: " +graphUrl);
            return;
        }

        JSONObject parameters = new JSONObject();

        try {
            parameters.put("key", "value");
        } catch (Exception e) {
            timber.log.Timber.d("Failed to put parameters: " + e.toString());
        }
        JsonObjectRequest request = new JsonObjectRequest(methodType.value, graphUrl,
                parameters,new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {
                /* Successfully called graph, process data and send to UI */
                timber.log.Timber.d("Response: " + response.toString());
                listener.onResponse(response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                timber.log.Timber.d("Error: " + error.toString());
                listener.onRequestFailed(0,error.toString());
            }
        }) {
            @Override
            public Map<String, String> getHeaders() {
                Map<String, String> headers = new HashMap<>();
                headers.put("Authorization", "Bearer " + authResult.getAccessToken());
                return headers;
            }
        };

        callGraphAPI(request);
    }

    protected void callGraphAPI(JsonObjectRequest request) {
        timber.log.Timber.d("Starting volley request to graph");
        timber.log.Timber.d("Adding HTTP GET to Queue, Request: " + request.toString());
        networkManager.sendRequest(3000,request);
    }


    /* Callback used in for silent acquireToken calls.
     * Looks if tokens are in the cache (refreshes if necessary and if we don't forceRefresh)
     * else errors that we need to do an interactive request.
     */
    private AuthenticationCallback getAuthTokenCallback() {
        return new AuthenticationCallback() {

            @Override
            public void onSuccess(IAuthenticationResult authenticationResult) {
                /* Successfully got a token, call graph now */
                timber.log.Timber.d("Successfully authenticated");
                /* Store the authResult */
                authResult = authenticationResult;
                serviceStarting =false;
            }

            @Override
            public void onError(MsalException exception) {
                /* Failed to acquireToken */
                timber.log.Timber.d("Authentication failed: " + exception.toString());

                if (exception instanceof MsalClientException) {
                    /* Exception inside MSAL, more info inside MsalError.java */
                } else if (exception instanceof MsalServiceException) {
                    /* Exception when communicating with the STS, likely config issue */
                } else if (exception instanceof MsalUiRequiredException) {
                    /* Tokens expired or no session, retry with interactive */
                }
                serviceStarting =false;
            }

            @Override
            public void onCancel() {
                /* User cancelled the authentication */
                timber.log.Timber.d("User cancelled login.");
                serviceStarting =false;
            }
        };
    }

    /* Callback used in for silent acquireToken calls.
     * Looks if tokens are in the cache (refreshes if necessary and if we don't forceRefresh)
     * else errors that we need to do an interactive request.
     */
    private AuthenticationCallback getAuthTokenCallback(AuthCallback authCallback) {
        return new AuthenticationCallback() {

            @Override
            public void onSuccess(IAuthenticationResult authenticationResult) {
                /* Successfully got a token, call graph now */
                timber.log.Timber.d("Successfully authenticated");

                /* Store the authResult */
                authResult = authenticationResult;

                authCallback.onSuccess();
            }

            @Override
            public void onError(MsalException exception) {
                /* Failed to acquireToken */
                timber.log.Timber.d("Authentication failed: " + exception.toString());

                if (exception instanceof MsalClientException) {
                    /* Exception inside MSAL, more info inside MsalError.java */
                } else if (exception instanceof MsalServiceException) {
                    /* Exception when communicating with the STS, likely config issue */
                } else if (exception instanceof MsalUiRequiredException) {
                    /* Tokens expired or no session, retry with interactive */
                }

                authCallback.onError(exception);
            }

            @Override
            public void onCancel() {
                /* User cancelled the authentication */
                timber.log.Timber.d("User cancelled login.");
                authCallback.onCancel();
            }
        };
    }

    //list
    @Override
    public void list(StateAbleRequestListener<List<TodoEntity>, I_ResponseState> listRequestListener){
        callGraphAPI(HttpMethod.GET, BASE_URL + TASK_ENDPOINT,new TodoEntityListRequestListener(listRequestListener));
    }

    @Override
    public void next(I_ResponseState state, StateAbleRequestListener<List<TodoEntity>, I_ResponseState> listRequestListener) {
        callGraphAPI(HttpMethod.GET, state.nextLinkUrl(),new TodoEntityListRequestListener(listRequestListener));
    }

    @Override
    public void list(QBFParameter qbfParameter, StateAbleRequestListener<List<TodoEntity>, I_ResponseState> listRequestListener){
        callGraphAPI(HttpMethod.GET, BASE_URL + TASK_ENDPOINT+ QBFBuilder.START_PREFIX+ QBFBuilder.getFilterStr(qbfParameter), new TodoEntityListRequestListener(listRequestListener));
    }

    /*public void listTodoFolder(RequestListener<List<TodoFolder>> listRequestListener){
        callGraphAPI(HttpMethod.GET, BASE_URL + TASK_FOLDER_ENDPOINT, EntityListListenerFactory.TODO_Folders(listRequestListener));
    }

    public void listTodoGroup(RequestListener<List<TodoGroup>> listRequestListener){
        callGraphAPI(HttpMethod.GET, BASE_URL + TASK_GROUP_ENDPOINT, EntityListListenerFactory.TODO_Groups(listRequestListener));
    }

    public void listWithTodoFolder(String folderId,RequestListener<List<TodoEntity>> listRequestListener){
        callGraphAPI(HttpMethod.GET, BASE_URL + TASK_FOLDER_ENDPOINT +"/"+folderId+ TASK_ENDPOINT, EntityListListenerFactory.TODO_Entitys(listRequestListener));
    }

    public void listWithTodoGroup(String groupId,RequestListener<List<TodoEntity>> listRequestListener){
        callGraphAPI(HttpMethod.GET, BASE_URL + TASK_GROUP_ENDPOINT +"/"+groupId+ TASK_ENDPOINT, EntityListListenerFactory.TODO_Entitys(listRequestListener));
    }

    public void listWithTodoGroupAndFolder(String groupId,String folderId,RequestListener<List<TodoEntity>> listRequestListener){
        callGraphAPI(HttpMethod.GET, BASE_URL + TASK_GROUP_ENDPOINT +"/"+groupId+TASK_FOLDER_ENDPOINT +"/"+folderId+ TASK_ENDPOINT, EntityListListenerFactory.TODO_Entitys(listRequestListener));
    }

    public void listTodoAttachments(String todoId,RequestListener<List<Attachment>> listRequestListener){
        callGraphAPI(HttpMethod.GET, BASE_URL + TASK_ENDPOINT+"/"+todoId+ TASK_ATTACHMENT_ENDPOINT, EntityListListenerFactory.TODO_Attachments(listRequestListener));
    }*/

    //get
    


    //post
    public void createTodoEntity(TodoEntity todoEntity) {

    }

    public void createTodoFolder(TodoEntity todoFolderEntity) {

    }

    public void createTodoGroup(TodoEntity todoGroupEntity) {

    }

    //complete
    public void completeTodoEntity(String todoId,StateAbleRequestListener<List<TodoEntity>, I_ResponseState> listRequestListener) {
        callGraphAPI(HttpMethod.POST, BASE_URL + TASK_ENDPOINT +"/"+todoId+"/complete", new TodoEntityListRequestListener(listRequestListener));
    }

    //delete
    public void  deleteTodoEntity(Activity activity,String todoId,RequestListener<TodoEntity> requestListener) {
        callGraphAPI(HttpMethod.DELETE, BASE_URL + TASK_ENDPOINT +"/"+todoId, EntityListenerFactory.TODO_Entity(requestListener));
    }

    public void deleteTodoFolder(Activity activity,String todoFolderId,RequestListener<TodoFolder> requestListener) {
        callGraphAPI(HttpMethod.DELETE, BASE_URL + TASK_FOLDER_ENDPOINT +"/"+todoFolderId,requestListener);
    }

    public void deleteTodoGroup(Activity activity,String todoGroupId,RequestListener<TodoGroup> requestListener) {
        callGraphAPI(HttpMethod.DELETE, BASE_URL + TASK_GROUP_ENDPOINT +"/"+todoGroupId,requestListener);
    }
}
